# -*- coding: utf-8 -*-
__author__ = 'lztkdr'
__date__ = '2020/3/27 15:03'

"""
    K近邻：根据你的邻居们的类别 判断你的类别。
    
	定义：
		如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别，则该样本也属于这个类别。
		k近邻公式计算距离，别名欧式距离。
	思想：
		相似的样本，特征之间的值应该都是相近的。
		
		
	K近邻算法示需要对数据 标准化（特征预处理） 处理的。
	
	====================================================================================================
	k-近邻算法优缺点
		优点：
			简单，易于理解，易于实现，无需估计参数，无需训练
		缺点：
			懒惰算法，对测试样本分类时的计算量大，内存开销大
			必须指定K值，K值选择不当则分类精度不能保证

	使用场景：小数据场景，几千～几万样本，具体场景具体业务去测试 
	
	====================================================================================================
	sklearn.neighbors.KNeighborsClassifier(n_neighbors=5,algorithm='auto')

	    n_neighbors：int,可选（默认= 5），k_neighbors查询默认使用的邻居数 

		algorithm：{‘auto’，‘ball_tree’，‘kd_tree’，‘brute’}，
			可选用于计算最近邻居的算法：‘ball_tree’将会使用 BallTree，‘kd_tree’将使用 KDTree。
			‘auto’将尝试根据传递给fit方法的值来决定最合适的算法。 (不同实现方式影响效率)
	
	数据处理：
		K近邻使用时，特征值必须进行标准化处理。
	====================================================================================================
	k近邻算法实例-预测入住位置
	
	https://www.kaggle.com/c/facebook-v-predicting-check-ins

	训练和测试数据集是根据时间划分的，测试数据中的公共/私人排行榜是随机划分的。此数据集中没有人的概念。所有row_id都是事件，而不是人员。 

	注意：某些列（例如时间和准确性）在其定义中故意含糊不清。请考虑将它们作为挑战的一部分。 

	档案说明
	train.csv，test.csv 
	row_id：签到事件的ID
	xy：坐标
	accuracy：位置精度 
	time：时间戳
	place_id：商家的ID，这是您要预测的目标
	sample_submission.csv-带有随机预测的正确格式的样本提交文件
	
	====================================================================================================
"""
import pandas as pd
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.neighbors import KNeighborsClassifier

# Pycharm输出窗口有省略号，数据显示不全解决方法
pd.set_option('display.max_rows', 10)
pd.set_option('display.max_columns', 200)
pd.set_option('display.width', 1000)

# 如果要显示中文标题和坐标表示，需要添加以下两行代码
from matplotlib import pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

print('pd.__version__:', pd.__version__)

print('============================================================================================================')

fb_location_dir = "F:\\MyCodes\\JupyterNotebook\\facebook-v-predicting-check-ins\\FBlocation\\"


def knncls():
	train_data_path = fb_location_dir + "train.csv"
	data = pd.read_csv(train_data_path)
	print(data.head())
	print("====================================================================================")
	# 处理数据
	#  数据太多，内存防止不足，缩小数据,查询数据筛选
	data = data.query("x > 1.0 &  x < 1.25 & y > 2.5 & y < 2.75")
	print("data.shape:\n", data.shape)
	print("========================================")
	# 处理时间的数据
	# unit s 单位：秒
	time_value = pd.to_datetime(data['time'], unit='s')
	print("time_value:\n", time_value)
	print("========================================")
	# 把日期格式转换成 字典格式
	time_value = pd.DatetimeIndex(time_value)

	# 关闭 SettingWithCopyWarning 警告
	pd.set_option('mode.chained_assignment', None)

	# 根据时间戳 构造一些新特征
	data['day'] = time_value.day
	data['hour'] = time_value.hour
	data['weekday'] = time_value.weekday

	# 把时间戳特征删除,axis=1 表示列
	data = data.drop(['time'], axis=1)
	print(data.head())
	print("========================================")
	"""
	 目标值太多了，那么预测的类别几千几万个，那样预测准确率会很低。
	"""
	# 把签到数量少于n个目标位置删除
	place_count = data.groupby('place_id').count()

	tf = place_count[place_count.row_id > 10].reset_index()

	data = data[data['place_id'].isin(tf.place_id)]

	# 取出数据当中的特征值和目标值

	# 目标值
	y = data['place_id']

	# 特征值 删除 目标值（place_id），以及与预测结果不相关的 特征(row_id)
	x = data.drop(['place_id', 'row_id'], axis=1)

	print("x.columns:\n", x.columns)
	print("========================================")
	# 进行数据的分割训练集合测试集
	x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.25)
	print('type(x_train):', type(x_train), 'type(y_test):', type(y_test))
	print("========================================")
	# 特征工程（标准化）很重要，可以提高预测的准确率
	std = preprocessing.StandardScaler()

	# 对 训练集的特征值 进行标准化
	x_train = std.fit_transform(x_train)

	# 对测试集的特征值 进行标准化
	x_test = std.transform(x_test)

	# 进行算法流程 # 超参数
	model = KNeighborsClassifier()

	# 输入 训练集
	# fit， predict,score
	model.fit(x_train, y_train)

	# 输入 测试集，得出预测结果
	y_predict = model.predict(x_test)

	print("预测的目标签到位置为：", y_predict)

	# 根据 测试集与测试结果 得出准确率
	print("预测的准确率:", model.score(x_test, y_test))

	print("========================================")
	"""
	模型的选择与调优:
		1、交叉验证
			将 训练集 再取出一部分 作为验证集，得出一个准确率。
		2、网格搜索
			网格搜索算法是一种通过遍历给定的参数组合来优化给定模型性能的方法。
	交叉验证：为了让被评估的模型更加准确可信
	================================================================================
	超参数搜索-网格搜索：
		通常情况下，有很多参数是需要手动指定的（如k-近邻算法中的K值），
		这种叫超参数。但是手动过程繁杂，所以需要对模型预设几种超参数组
		合。每组超参数都采用交叉验证来进行评估。最后选出最优参数组合建
		立模型。
		
	网格搜索：
		sklearn.model_selection.GridSearchCV(estimator, param_grid=None,cv=None)
		对估计器的指定参数值进行详尽搜索
		参数：
			estimator：估计器对象
			param_grid：估计器参数(dict){“n_neighbors”:[1,3,5]}
			cv：指定几折交叉验证
		
		方法：
			fit：输入训练数据
			score：准确率
		结果分析：
			best_score_:在交叉验证中测试的最好结果
			best_estimator_：最好的参数模型
			cv_results_:每次交叉验证后的验证集准确率结果和训练集准确率结果	
			

	"""

	# 构造一些参数的值进行搜索
	param = {"n_neighbors": [3, 5, 10]}

	# 进行 网格搜索+交叉验证
	# 网格搜索算法是一种通过遍历给定的参数组合来优化给定模型性能的方法。
	# CV：交叉验证的意思，cross validation。
	# cv = 2,表示每个参数有 2个 结果。
	# 'params': [{'n_neighbors': 3}, {'n_neighbors': 5}, {'n_neighbors': 10}],
	# 'split0_test_score': array([0.44614135, 0.46011373, 0.45702681]),
	# 'split1_test_score': array([0.44813414, 0.46835443, 0.4601348 ])
	gc = GridSearchCV(model, param_grid=param, cv=2)

	gc.fit(x_train, y_train)

	# 预测准确率
	print("在测试集上准确率：\n", gc.score(x_test, y_test))
	print("==========================")
	print("在交叉验证当中最好的结果：\n", gc.best_score_)
	print("==========================")
	print("选择最好的模型是：\n", gc.best_estimator_)
	print("==========================")
	print("每个超参数每次交叉验证的结果：\n", gc.cv_results_)

	return None


if __name__ == "__main__":
	knncls()
